package main

import (
	"backend/cmd/command"
	"backend/config"
	"backend/pkg/utils"
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"syscall"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/gin-gonic/gin"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

var (
	Version      string
	configPath   string
	conf         *config.Configuration
	loggerWriter *lumberjack.Logger
	logger       *zap.Logger
)

func init() {
	// 只保留一个init函数，使用--config参数
	pflag.StringVarP(&configPath, "config", "", "conf/config.yaml", "config file path")
	cobra.OnInitialize(func() {
		initConfig()
		initLogger()
	})
}

func main() {
	fmt.Println("=== 程序开始执行 ===")
	rootCmd := &cobra.Command{
		Use: "app",
		Run: func(cmd *cobra.Command, args []string) {
			fmt.Println("=== 开始执行app命令 ===")
			fmt.Println("正在准备调用wireApp创建应用实例...")
			fmt.Printf("配置对象: %p, 日志写入器: %p, 日志对象: %p\n", conf, loggerWriter, logger)
			fmt.Println("开始调用wireApp...")
			app, cleanup, err := wireApp(conf, loggerWriter, logger)
			fmt.Println("wireApp调用完成")
			if err != nil {
				fmt.Printf("wireApp创建失败: %v\n", err)
				// 即使依赖初始化失败，也尝试启动HTTP服务器
				fmt.Println("尽管依赖初始化失败，仍尝试启动HTTP服务器...")
				if serverErr := startMinimalServer(conf); serverErr != nil {
					fmt.Printf("启动最小服务器失败: %v\n", serverErr)
					panic(serverErr)
				}
				// 等待中断信号以优雅地关闭最小服务器
				fmt.Println("正在等待中断信号...")
				quit := make(chan os.Signal)
				signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
				<-quit
				return
			}
			fmt.Println("wireApp创建成功，获得应用实例")
			defer cleanup()

			// 启动应用
			fmt.Printf("开始启动应用 %s ...\n", Version)
			log.Printf("start app %s ...", Version)
			if err := app.Run(); err != nil {
				fmt.Printf("应用启动失败: %v\n", err)
				panic(err)
			}
			fmt.Println("应用启动成功，进入等待状态")

			// 等待中断信号以优雅地关闭应用
			fmt.Println("正在等待中断信号...")
			quit := make(chan os.Signal)
			signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
			<-quit

			fmt.Println("收到中断信号，正在关闭应用...")
			log.Printf("shutdown app %s ...", Version)

			// 设置 5 秒的超时时间
			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()

			// 关闭应用
			if err := app.Stop(ctx); err != nil {
				fmt.Printf("应用关闭失败: %v\n", err)
				panic(err)
			}
			fmt.Println("应用关闭成功")
		},
	}
	command.Register(rootCmd, func() (*command.Command, func(), error) {
		return provideCommand(conf, loggerWriter, logger)
	})

	if err := rootCmd.Execute(); err != nil {
		panic(err)
	}
}

// startMinimalServer 启动一个最小化的HTTP服务器，即使在依赖初始化失败的情况下也能提供基本响应
func startMinimalServer(conf *config.Configuration) error {
	// 创建一个简单的Gin路由
	router := gin.Default()

	// 添加基本的健康检查路由
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"status":  "ok",
			"message": "服务正在运行，但某些依赖可能未连接",
			"time":    time.Now().Format(time.RFC3339),
		})
	})

	// 添加根路由
	router.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"app_name": conf.App.AppName,
			"status":   "running (minimal mode)",
			"note":     "数据库和Redis连接可能未建立",
		})
	})

	// 启动服务器
	serverAddr := fmt.Sprintf(":%d", conf.App.Port)
	fmt.Printf("正在启动最小化HTTP服务器，监听地址: %s\n", serverAddr)

	return router.Run(serverAddr)
}

func initConfig() {
	fmt.Println("=== 开始初始化配置 ===")
	// 直接设置为项目根目录下的配置文件路径
	// 因为 Makefile 中是在 cmd 目录下运行的，所以需要返回上一级目录
	currentDir, _ := os.Getwd()
	fmt.Printf("当前工作目录: %s\n", currentDir)
	// 如果当前在 cmd 目录下，使用项目根目录
	if filepath.Base(currentDir) == "cmd" {
		configPath = filepath.Join(currentDir, "..", "conf", "config.yaml")
	} else {
		configPath = filepath.Join(currentDir, "conf", "config.yaml")
	}

	fmt.Printf("加载配置文件: %s\n", configPath)

	v := viper.New()
	v.SetConfigFile(configPath)
	v.SetConfigType("yaml")
	if err := v.ReadInConfig(); err != nil {
		fmt.Printf("配置文件读取失败: %v\n", err)
		panic(fmt.Errorf("read config failed: %s \n", err))
	}
	fmt.Println("配置文件读取成功")

	if err := v.Unmarshal(&conf); err != nil {
		fmt.Printf("配置解析失败: %v\n", err)
	} else {
		fmt.Println("配置解析成功")
	}
	fmt.Printf("应用端口: %s\n", conf.App.Port)
	fmt.Printf("数据库驱动: %s\n", conf.Db.Driver)
	fmt.Println("=== 配置初始化完成 ===")

	v.WatchConfig()
	v.OnConfigChange(func(in fsnotify.Event) {
		fmt.Println("config file changed:", in.Name)
		defer func() {
			if err := recover(); err != nil {
				logger.Error("config file changed err:", zap.Any("err", err))
				fmt.Println(err)
			}
		}()
		if err := v.Unmarshal(&conf); err != nil {
			fmt.Println(err)
		}
	})
}

func initLogger() {
	fmt.Println("=== 开始初始化日志系统 ===")
	var level zapcore.Level  // zap 日志等级
	var options []zap.Option // zap 配置项

	fmt.Printf("日志根目录配置: %s\n", conf.Log.RootDir)
	logFileDir := conf.Log.RootDir
	if !filepath.IsAbs(logFileDir) {
		fmt.Println("日志目录不是绝对路径，正在转换...")
		rootPath := utils.RootPath()
		fmt.Printf("获取到的根路径: %s\n", rootPath)
		logFileDir = filepath.Join(rootPath, logFileDir)
	}
	fmt.Printf("完整日志目录: %s\n", logFileDir)

	fmt.Println("正在创建日志目录...")
	if err := utils.CreateDirIfNotExist(logFileDir); err != nil {
		fmt.Printf("创建日志目录失败: %v\n", err)
		panic(fmt.Errorf("create log directory failed: %s", err))
	}
	fmt.Println("日志目录创建成功")
	switch conf.Log.Level {
	case "debug":
		level = zap.DebugLevel
		options = append(options, zap.AddStacktrace(level))
	case "info":
		level = zap.InfoLevel
	case "warn":
		level = zap.WarnLevel
	case "error":
		level = zap.ErrorLevel
		options = append(options, zap.AddStacktrace(level))
	case "dpanic":
		level = zap.DPanicLevel
	case "panic":
		level = zap.PanicLevel
	case "fatal":
		level = zap.FatalLevel
	default:
		level = zap.InfoLevel
	}

	// 调整编码器默认配置
	encoderConfig := zap.NewProductionEncoderConfig()
	encoderConfig.EncodeTime = func(time time.Time, encoder zapcore.PrimitiveArrayEncoder) {
		encoder.AppendString(time.Format("2006-01-02 15:04:05.000"))
	}
	encoderConfig.EncodeLevel = func(l zapcore.Level, encoder zapcore.PrimitiveArrayEncoder) {
		encoder.AppendString(conf.App.Env + "." + l.String())
	}

	loggerWriter = &lumberjack.Logger{
		Filename:   filepath.Join(logFileDir, conf.Log.Filename),
		MaxSize:    conf.Log.MaxSize,
		MaxBackups: conf.Log.MaxBackups,
		MaxAge:     conf.Log.MaxAge,
		Compress:   conf.Log.Compress,
	}

	logger = zap.New(zapcore.NewCore(zapcore.NewJSONEncoder(encoderConfig), zapcore.AddSync(loggerWriter), level), options...)
}
